package com.jiangyg.mall.ware.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jiangyg.mall.core.exception.BusinessException;
import com.jiangyg.mall.core.support.restful.Pagination;
import com.jiangyg.mall.core.support.snowflake.SnowflakeBuilder;
import com.jiangyg.mall.core.utils.Logger;
import com.jiangyg.mall.db.mybatis.BaseServiceImpl;
import com.jiangyg.mall.order.dto.OrderReleaseDTO;
import com.jiangyg.mall.order.enums.OrderStatus;
import com.jiangyg.mall.order.service.OrderService;
import com.jiangyg.mall.product.service.SkuService;
import com.jiangyg.mall.product.vo.SkuInfoVO;
import com.jiangyg.mall.ware.constant.ColumnConstant;
import com.jiangyg.mall.ware.dao.WareInfoDao;
import com.jiangyg.mall.ware.dao.WareOrderTaskDao;
import com.jiangyg.mall.ware.dao.WareOrderTaskDetailDao;
import com.jiangyg.mall.ware.dao.WareSkuDao;
import com.jiangyg.mall.ware.dto.StockReleaseDTO;
import com.jiangyg.mall.ware.dto.WareLockStockDTO;
import com.jiangyg.mall.ware.dto.WareSkuDTO;
import com.jiangyg.mall.ware.entity.WareInfo;
import com.jiangyg.mall.ware.entity.WareOrderTask;
import com.jiangyg.mall.ware.entity.WareOrderTaskDetail;
import com.jiangyg.mall.ware.entity.WareSku;
import com.jiangyg.mall.ware.enums.OrderTaskStatus;
import com.jiangyg.mall.ware.vo.WareSkuVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Service
public class WareSkuServiceImpl extends BaseServiceImpl<WareSkuDao, WareSku> implements WareSkuService {

//    @Reference
    @Autowired
    private SkuService skuService;

//    @Reference
    @Autowired
    private OrderService orderService;

    private final WareInfoDao wareInfoDao;

    private final RabbitTemplate rabbitTemplate;

    private final WareOrderTaskDao wareOrderTaskDao;

    private final WareOrderTaskDetailDao wareOrderTaskDetailDao;

    private final DataSourceTransactionManager transactionManager;

    @Autowired
    public WareSkuServiceImpl(WareSkuDao wareSkuDao,
                              WareInfoDao wareInfoDao,
                              RabbitTemplate rabbitTemplate,
                              WareOrderTaskDao wareOrderTaskDao,
                              WareOrderTaskDetailDao wareOrderTaskDetailDao,
                              DataSourceTransactionManager transactionManager) {
        super(wareSkuDao);
        this.wareInfoDao = wareInfoDao;
        this.rabbitTemplate = rabbitTemplate;
        this.wareOrderTaskDao = wareOrderTaskDao;
        this.wareOrderTaskDetailDao = wareOrderTaskDetailDao;
        this.transactionManager = transactionManager;
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
    public Pagination<WareSkuVO> selectPagination(Pagination<WareSkuVO> pagination) throws Exception {
        // 执行查询
        pagination = this.executeSelectPagination(pagination, WareSkuVO.class);
        {
            final List<WareSkuVO> dataList = pagination.getRecords();
            if (CollectionUtils.isEmpty(dataList)) {
                return pagination;
            }
            // 仓库名称反写
            final List<Long> wareIdList = dataList.stream()
                    .map(d -> Long.parseLong(d.getWareId())).distinct().collect(Collectors.toList());
            final QueryWrapper<WareInfo> wrapper = new QueryWrapper<>();
            wrapper.in(ColumnConstant.ID, wareIdList);
            final List<WareInfo> wareInfoList = this.wareInfoDao.selectList(wrapper);
            final Map<String, String> wareMap = wareInfoList.stream()
                    .collect(Collectors.toMap(k -> k.getId().toString(), WareInfo::getName));
            dataList.forEach(data -> data.setWareName(MapUtils.getString(wareMap, data.getWareId())));
        }
        return pagination;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrUpdate(WareSkuDTO dto) throws Exception {
        // 判断SKUID是否存在
        final SkuInfoVO skuInfo = skuService.selectById(dto.getSkuId());
        if (skuInfo == null) {
            throw BusinessException.instance("mall.ware.skuinfo.notexist");
        }
        dto.setSkuName(skuInfo.getName());
        // 执行保存
        // TODO 库存数和锁定库存数需要处理（没想好~~~）
        return this.executeSaveOrUpdate(dto) > 0;
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
    public WareSkuVO selectById(Long wareSkuId) throws Exception {
        return this.executeSelectById(wareSkuId, WareSkuVO.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean delete(List<Long> wareSkuIds) {
        return this.baseMapper.deleteBatchIds(wareSkuIds) > 0;
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
    public Map<Long, Boolean> inStock(List<Long> skuIds) {
        if (CollectionUtils.isEmpty(skuIds)) {
            return MapUtils.EMPTY_MAP;
        }
        // TODO 返回格式待优化，最好返回VO
        // TODO 待优化，根据用户收货地址查询最近仓库是否有库存
        // 查询库存剩余数量
        final List<Map<String, Object>> mapList = this.baseMapper.selectSkuStockInfo(skuIds);
        // 格式转换
        return mapList.stream()
                .collect(Collectors.toMap(
                        k -> MapUtils.getLong(k, ColumnConstant.SKU_ID),
                        v -> MapUtils.getLong(v, "surplus") > 0));
    }

    @Override
    public boolean lockStock(WareLockStockDTO dto) {
        TransactionStatus status = null;
        try {
            // 1. 获取事务定义，设置事务隔离级别（新事务）
            final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
            def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
            status = transactionManager.getTransaction(def);
            // 2. 生成工作单ID
            final Long taskId = SnowflakeBuilder.nextId();
            // TODO 性能优化，尝试保存变批量保存
            // 3. 生成库存工作单
            this.saveWareOrderTask(taskId, dto);
            // 4. 生成库存工作单明细
            this.saveWareOrderTaskDetails(taskId, dto);
            // 5. 提交事物
            transactionManager.commit(status);
            return true;
        } catch (Exception ex) {
            Logger.error(log, () -> "[锁定库存]-锁定库存失败", ex);
            if (status != null) {
                transactionManager.rollback(status);
            }
            return false;
        }
    }

    /**
     * 功能描述：按照参数生成工作单
     *
     * @param taskId 工作单ID
     * @param dto    参数
     */
    private void saveWareOrderTask(Long taskId, WareLockStockDTO dto) {
        final WareOrderTask wareOrderTask = new WareOrderTask();
        wareOrderTask.setId(taskId);
        wareOrderTask.setOrderId(dto.getOrderId());
        wareOrderTask.setReceiverName(dto.getReceiverName());
        wareOrderTask.setReceiverPhone(dto.getReceiverPhone());
        wareOrderTask.setReceiverDetailAddress(dto.getReceiverDetailAddress());
        wareOrderTask.setRemark(dto.getRemark());
        wareOrderTask.setStatus(OrderTaskStatus.LOCKED.code);
        wareOrderTask.setCreateTime(new Date());
        this.wareOrderTaskDao.insert(wareOrderTask);
    }

    /**
     * 功能描述：保存工作单明细
     *
     * @param taskId 工作单ID
     * @param dto    参数
     */
    private void saveWareOrderTaskDetails(Long taskId, WareLockStockDTO dto) {
        // TODO 应该有更好的实现方式，还需要实现就近查找仓库
        for (WareLockStockDTO.Detail detail : dto.getDetails()) {
            // 查询商品有库存的仓库列表
            final QueryWrapper<WareSku> wrapper = new QueryWrapper<>();
            wrapper.eq(ColumnConstant.SKU_ID, detail.getSkuId());
            wrapper.last("stock - locked >= " + detail.getSkuQuantity());
            final List<WareSku> wareSkuList = this.baseMapper.selectList(wrapper);
            // 如果商品查询没有库存则直接返回锁定失败
            if (CollectionUtils.isEmpty(wareSkuList)) {
                throw BusinessException.instance("mall.ware.sku.no.stock.error");
            }
            // 用来标记商品锁定库存是否成功
            boolean success = false;
            // 对查询出来的仓库尝试进行库存锁定
            for (WareSku ware : wareSkuList) {
                // 如果锁定成功则进行下一个商品的锁定，否则换仓库锁库存
                final int count = this.baseMapper.lockSkuStock(ware.getId(), detail.getSkuQuantity());
                if (count == 1) {
                    // 保存工作单详情信息
                    final WareOrderTaskDetail wareOrderTaskDetail = new WareOrderTaskDetail();
                    wareOrderTaskDetail.setId(SnowflakeBuilder.nextId());
                    wareOrderTaskDetail.setTaskId(taskId);
                    wareOrderTaskDetail.setSkuId(detail.getSkuId());
                    wareOrderTaskDetail.setSkuName(detail.getSkuName());
                    wareOrderTaskDetail.setSkuQuantity(detail.getSkuQuantity());
                    wareOrderTaskDetail.setStatus(OrderTaskStatus.LOCKED.code);
                    this.wareOrderTaskDetailDao.insert(wareOrderTaskDetail);
                    // 发送延期消息，在50分钟之后对应的订单还没有支付则触发解锁处理
                    final StockReleaseDTO stockReleaseDTO = new StockReleaseDTO();
                    stockReleaseDTO.setOrderId(dto.getOrderId());
                    stockReleaseDTO.setOrderTaskId(taskId);
                    stockReleaseDTO.setSkuId(detail.getSkuId());
                    stockReleaseDTO.setOrderTaskDetailId(wareOrderTaskDetail.getId());
                    rabbitTemplate.convertAndSend("stock-event-exchange", "stock.locked.main", stockReleaseDTO);
                    success = true;
                    break;
                }
            }
            // 如果所有仓库都锁定失败，说明商品已经没有库存了
            if (!success) {
                throw BusinessException.instance("mall.ware.sku.no.stock.error");
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unlockStock(OrderReleaseDTO dto) {
        // 1. 根据订单ID查询库存工作单
        final WareOrderTask task;
        {
            final QueryWrapper<WareOrderTask> wrapper = new QueryWrapper<>();
            wrapper.eq(ColumnConstant.ORDER_ID, dto.getId());
            task = this.wareOrderTaskDao.selectOne(wrapper);
            if (task == null) {
                Logger.info(log, () -> String.format("[解锁库存]-订单[%d]解锁库存失败，没有找到工作单信息！", dto.getId()));
                return;
            }
        }
        // 2. 查询订单下所有工作单明细
        final QueryWrapper<WareOrderTaskDetail> wrapper = new QueryWrapper<>();
        wrapper.eq(ColumnConstant.TASK_ID, task.getId());
        wrapper.eq(ColumnConstant.STATUS, OrderTaskStatus.LOCKED.code);
        final List<WareOrderTaskDetail> taskDetailList = this.wareOrderTaskDetailDao.selectList(wrapper);
        // 3. 解锁订单下所有商品的库存
        for (WareOrderTaskDetail detail : taskDetailList) {
            unlockStock(detail);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unlockStock(StockReleaseDTO dto) {
        // 1. 查询工作单详情（如果库存单不存在或者状态不为锁定则直接退出）
        final WareOrderTaskDetail detail = this.wareOrderTaskDetailDao.selectById(dto.getOrderTaskDetailId());
        if (detail == null || OrderTaskStatus.decide(detail.getStatus()) != OrderTaskStatus.LOCKED) {
            return;
        }
        // 2. 查询订单状态
        // TODO 此处大概率出错，dubbo可能不支持枚举序列化
        final OrderStatus orderStatus = this.orderService.selectOrderStatus(dto.getOrderId());
        // 3. 如果订单不存在或者订单已取消则进行库存解锁（上面已经进行工作单状态判断，所以此处工作单一定为锁定状态）
        if (orderStatus == null || orderStatus == OrderStatus.CANCELLED) {
            unlockStock(detail);
        }
    }

    /**
     * 功能描述：解锁商品库存
     *
     * @param detail 工作单详情
     */
    private void unlockStock(WareOrderTaskDetail detail) {
        // 1. 解锁库存
        this.baseMapper.unlockSkuStock(detail.getSkuId(), detail.getWareId(), detail.getSkuQuantity());
        // 2. 更新工作单状态
        final WareOrderTaskDetail updateDetail = new WareOrderTaskDetail();
        updateDetail.setId(detail.getId());
        updateDetail.setStatus(OrderTaskStatus.UNLOCK.code);
        this.wareOrderTaskDetailDao.updateById(updateDetail);
    }

}
